/*
 * Copyright 2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import classNames from "classnames"
import * as _ from "lodash"
import * as React from "react"
import { HTMLAttributes, useState } from "react"
import Divider from "../divider"
import Input from "../input"
import { componentClassName, prefixClassName } from "../style"
import Swatch from "../switch"
import Textarea from "../textarea"
import "./index.scss"

class FormPropertyDescriptor {

  public readonly name: string

  public getValue: (() => any) | undefined

  public setValue: ((value: any) => void) | undefined

  public constructor(name: string) {
    this.name = name
  }
}

export class FormObject {

  private descriptors: { [key: string]: FormPropertyDescriptor } = {}

  public name: string

  constructor(name: string, initialValues?: { [key: string]: any }) {
    this.name = name
    if (initialValues) {
      this.setFieldsValue(initialValues)
    }
  }

  public getFieldsValue(fieldNames?: string[]) {
    const values = {}
    _.transform(this.descriptors, (result: any, value, key) => {
      if (value.getValue) {
        if (fieldNames) {
          if (_.includes(fieldNames, key)) {
            result[key] = value.getValue()
          }
        } else {
          result[key] = value.getValue()
        }
      }
    }, values)

    return values
  }

  public setFieldsValue(values: { [key: string]: any } = {}) {
    _.forEach(values, (value, key) => {
      const descriptor = this.descriptors[key]
      if (descriptor && descriptor.setValue) {
        descriptor.setValue(value)
      }
    })
  }

  public addPropertyDescriptor(descriptor: FormPropertyDescriptor) {
    this.descriptors[descriptor.name] = descriptor
  }

  public getPropertyDescriptor(name: string) {
    return this.descriptors[name]
  }
}

const FormContext = React.createContext(new FormObject(""))

interface InputComponentProps {
  form: FormObject;
  element: React.ReactElement<React.InputHTMLAttributes<HTMLInputElement>>
}

function InputComponent({ form, element }: InputComponentProps) {

  const [defaultChecked] = useState(element.props.defaultChecked)
  const [checked, setChecked] = useState(element.props.checked || false)
  const [defaultValue] = useState(element.props.defaultValue)
  const [value, setValue] = useState(element.props.value || "")
  const elementType = element.type

  // const filedName = element.props.name;

  // function useSetValue(setValue) {
  //     if (filedName) {
  //         const pd = new FormPropertyDescriptor(filedName);
  //         form.addPropertyDescriptor(pd);
  //         pd.setValue = setValue;
  //     }
  // }
  //
  // function useGetValue(value) {
  //     if (filedName) {
  //         const pd = form.getPropertyDescriptor(filedName);
  //         pd.getValue = () => value;
  //     }
  // }

  if (elementType === Swatch) {
    // useSetValue(setChecked);
    // useGetValue(checked);

    return React.cloneElement(element, {
      defaultChecked,
      checked,
      onChange: (e: any) => setChecked(e.target.checked),
    })
  } else {
    // useSetValue(setValue);
    // useGetValue(value);

    return React.cloneElement(element, {
      defaultValue,
      value,
      onChange: (e: any) => setValue(e.target.value),
    })
  }
}

class InputWrapper {

  private static InputTypes: any[] = [Input, Swatch, Textarea]

  private static isInputType(element: React.ReactElement): boolean {
    return InputWrapper.InputTypes.includes(element.type)
  }

  public static wrapper(element: React.ReactElement) {
    const { isInputType } = InputWrapper
    if (!isInputType(element)) {
      return element
    }

    return <FormContext.Consumer>
      {value => <InputComponent form={value} element={element}/>}
    </FormContext.Consumer>
  }
}

interface FormItemProps extends HTMLAttributes<HTMLDivElement> {
  label?: React.ReactNode
}

class FormItem extends React.Component<FormItemProps, any> {
  public render() {
    const { className } = this.props
    return (
      <>
        <div className={classNames(prefixClassName("form-item"), className)}>
          <div className={prefixClassName("form-item-wrapper")}>
            {this.renderLabelElement()}
            <FormFieldDecorator children={this.props.children}/>
          </div>
          <Divider hairline={false}/>
        </div>
      </>
    )
  }

  public renderLabelElement() {
    let { label } = this.props

    if (_.isUndefined(label)) {
      return
    }

    if (_.isEmpty(label) || _.isString(label) || _.isNumber(label)) {
      label = <label>{label}</label>
    }

    return label
  }
}

class FormFieldDecorator extends React.Component {
  public render() {
    // @ts-ignore
    return (React.Children.map(this.props.children, InputWrapper.wrapper))
  }
}

interface FormProps {
  form: FormObject
}

interface FormState {

}

class Form extends React.Component<FormProps, FormState> {

  public static Item = FormItem

  public static FieldDecorator = FormFieldDecorator

  protected static defaultProps = {
    form: new FormObject(""),
  }

  public static createForm(name: string, initialValues?: { [key: string]: any }) {
    return new FormObject(name, initialValues)
  }

  public render() {
    const { children, form } = this.props
    return (
      <FormContext.Provider value={form}>
        <div className={componentClassName("form")}>
          {children}
        </div>
      </FormContext.Provider>
    )
  }
}

export default Form
